#pragma once
#ifndef _KH_ENGINE_ENGINE_H
#define _KH_ENGINE_ENGINE_H
#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Units.h>
#include <Kharlia/Engine/Level.h>
#include <Kharlia/Engine/TileData.h>
#include <Kharlia/Engine/Net.h>
#include <Kharlia/Engine/Resource.h>
#include <Kharlia/Core/Logger.h>
#include <Kharlia/Core/Subsystem.h>

#include <CEGUIDefaultResourceProvider.h>
#include <CEGUIInputEvent.h>

namespace kharlia { namespace engine {

class CActor;
class CWorld;
class CPlayer;
class CClient;

void _InitEngineClasses();

// TODO: Organize the public members for CEngine

class KH_ENGINE_API CEngine: public core::CSubsystem, public FNetHandler {
    shared_ptr<core::FLogger> mLogger;
    bool mInited;
    bool mInitedClient;
    bool mIsServer;
    bool mIsClient;
    bool mIsEditor;
    float mTickElapsed;
    TPtr<CWorld> mWorld;
    object mConfig;
    TPtr<CPlayer> mLocalPlayer;
    shared_ptr<FResourceManager> mResourceManager;
    TPtr<CClient> mClient;
    uint16 mClientTickRate;
    float mClientTickElapsed;
    // InputTarget

    FNetPeer* mNetPeer;

    // Editor
    bool mEditorEnabled; // toggle for editor functionality
    bool mEditorClampBrushPos; // true if brush position is clamped to tiles (floored)
    shared_ptr<FTileData> mEditorBrush;
    bool mEditorBrushVisible;
    FVector2F mEditorBrushPos;
    bool mEditorGridVisible;
    FRectF mEditorSelection;
    bool mEditorSelectionVisible;
    shared_ptr<FTileLayer> mEditorBrushLayer;

    static CEngine* msInstance;

    KH_DECL_GC()

protected:
    bool ExecImpl(object cmd, object out);
    void InitImpl();
    void ExitImpl();

public:
    CEngine(PyObject* self);
    virtual ~CEngine();

    void Init();
    void Exit();
    virtual void InitClient(object viewport);
    virtual void ExitClient();
    object GetClientClass() const {
        return call_method<object>(mSelf, "GetClientClass");
    }
    virtual void Tick(float deltaTime);
    virtual void TaskletMain();
    TPtr<CWorld> LoadWorld(object path);
    virtual bool EnsureLocalPlayer();
    object GetLocalPlayerClass() const {
        return call_method<object>(mSelf, "GetLocalPlayerClass");
    }
    bool Exec(object cmd, object out=object());
    object ResolvePath(object fpath) {
        return call_method<object>(mSelf, "ResolvePath", fpath);
    }

    shared_ptr<core::FLogger> const& GetLogger() const { return mLogger; }
    bool GetInited() const { return mInited; }
    bool GetInitedClient() const { return mInitedClient; }
    bool GetIsServer() const { return mIsServer; }
    bool GetIsClient() const { return mIsClient; }
    bool GetIsEditor() const { return mIsEditor; }
    TPtr<CWorld> const& GetWorld() const { return mWorld; }
    void SetWorld(TPtr<CWorld> const& v);
    object const& GetConfig() const { return mConfig; }
    void SetConfig(object const& v) { mConfig = v; }
    TPtr<CPlayer> const& GetLocalPlayer() const { return mLocalPlayer; }
    shared_ptr<FResourceManager> const& GetResourceManager() const { return mResourceManager; }
    TPtr<CClient> const& GetClient() const { return mClient; }
    uint16 GetClientTickRate() const { return mClientTickRate; }
    void SetClientTickRate(uint16 v) { mClientTickRate = v; }

    void Connect(str host, uint16 port);
    virtual void OnServerConnected(rn::RakNetGUID guid, rn::SystemAddress addr);
    // Key events called when the client receives a key
    void OnKeyDown(ui::Key::Scan keycode);
    void OnKeyUp(ui::Key::Scan keycode);
    void OnChar(ui::utf32 codepoint);


    // Editor
    bool GetEditorEnabled() const { return mEditorEnabled; }
    inline void SetEditorEnabled(bool value) {
        mEditorEnabled = mIsEditor = value;
    }
    // Editor Brush
    shared_ptr<FTileData> const& GetEditorBrush() const { return mEditorBrush; }
    void SetEditorBrush(shared_ptr<FTileData> const& b);
    shared_ptr<FTileLayer> const& GetEditorBrushLayer() const {
        return mEditorBrushLayer;
    }
    bool GetEditorBrushVisible() const { return mEditorBrushVisible; }
    void SetEditorBrushVisible(bool v) { mEditorBrushVisible = v; }
    FVector2F const& GetEditorBrushPos() const { return mEditorBrushPos; }
    void SetEditorBrushPos(FVector2F const& p) {
        mEditorBrushPos = p;
        if (mEditorClampBrushPos) {
            mEditorBrushPos.X = floor(mEditorBrushPos.X);
            mEditorBrushPos.Y = floor(mEditorBrushPos.Y);
        }
    }
    // Editor Selection
    FRectF const& GetEditorSelection() const { return mEditorSelection; }
    void SetEditorSelection(FRectF const& v) { mEditorSelection = v; }
    bool GetEditorSelectionVisible() const { return mEditorSelectionVisible; }
    void SetEditorSelectionVisible(bool v) { mEditorSelectionVisible = v; }
    bool GetEditorGridVisible() const { return mEditorGridVisible; }
    void SetEditorGridVisible(bool v) { mEditorGridVisible = v; }

    //void SetEditorSelectionPy(tuple position);
    //void SetEditorSelection(FVector2I topleft, FVector2I botright);

    friend void _InitEngineClasses();
    friend void _InitEngineBindings();
    friend TPtr<CEngine> GetEngine();
    friend CEngine* GetEnginePtr();
    friend struct FEngineRenderInfo;
};

inline void CEngine::Init() {
    call_method<void>(mSelf, "Init");
}

inline void CEngine::Exit() {
    call_method<void>(mSelf, "Exit");
}

inline TPtr<CEngine> GetEngine() {
    return CEngine::msInstance ? CEngine::msInstance->GetSelfPtr<CEngine>() : TPtr<CEngine>();
}

inline CEngine* GetEnginePtr() {
    return CEngine::msInstance;
}

}} //namespace Kharlia

ENABLE_BACK_REFERENCE(kharlia::engine::CEngine)

#endif
